// Copyright (C) 2010 Razvan Constantin <razvan.constantin@rconst.net>
//
// This file is part of FileBow.
//
// FileBow is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// FileBow is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with FileBow.  If not, see <http://www.gnu.org/licenses/>.

#include "logic/path-tree.h"

#include <iostream> // debug
#include <memory>
#include <stack>

#include <gtest/gtest.h>
#include <Poco/Path.h>

using namespace std;
using Poco::Path;

namespace filebow {

class PathNodeTest : public ::testing::Test {
  protected: PathNode* Insert(PathNode* parent, PathNode* child) {
    return parent->Insert(child);
  }
  protected: void Remove(PathNode* node) {
    node->Remove();
  }
};

class PathTreeTest : public ::testing::Test {
  public: virtual void SetUp() {
    tree.reset(new PathTree());
    tree->Insert("/testdata/bar");
    tree->Insert("/testdata/baz.txt");
    tree->Insert("/testdata/foo.txt");
    tree->Insert("/testdata/subdir/afile.txt");
    tree->Insert("/testdata/subdir/bfile.txt");
  }
  public: virtual void TearDown() {
    tree.reset(NULL);
  }

  protected: auto_ptr<PathTree> tree;
};

class MockWalker : public PathTree::Walker {
  public: MockWalker()
      : enterCount(0), exitCount(0), fileCount(0) {
  }
  public: virtual void EnterDir(const PathTree::Node* dirNode) {
    ++enterCount;
    skip.push(dirNode->name() == "skip");
  };
  public: virtual bool SkipDir() {
    return skip.empty() ? false : skip.top();
  }
  public: virtual void ExitDir() {
    ++exitCount;
    if (!skip.empty()) {
      skip.pop();
    }
  }
  public: virtual void VisitFile(const PathTree::Node* fileNode) {
    ++fileCount;
  }
  public: int enterCount;
  public: int exitCount;
  public: int fileCount;
  public: stack<bool> skip;
};

TEST_F(PathNodeTest, TestInsertRemove) {
  auto_ptr<PathNode> root(new PathNode(PathNode::ROOT, "root"));
  EXPECT_EQ(0, root->size());
  PathNode* n1 = Insert(root.get(), new PathNode(PathNode::DIRECTORY, "n1"));
  EXPECT_EQ(1, root->size());
  EXPECT_EQ(root.get(), n1->parent());
  EXPECT_EQ(root->child(), n1);
  PathNode* n11 = Insert(n1, new PathNode(PathNode::FILE, "n11"));
  EXPECT_EQ(1, root->size());
  PathNode* n2 = Insert(root.get(), new PathNode(PathNode::FILE, "n2"));
  EXPECT_EQ(2, root->size());
  EXPECT_EQ(root.get(), n2->parent());
  Remove(n1);
  EXPECT_EQ(1, root->size());
  EXPECT_EQ(NULL, n1->parent());
  EXPECT_EQ(NULL, n1->sibling());
  EXPECT_EQ(root->child(), n2);
  EXPECT_EQ(NULL, n2->sibling());
  delete n1;
}

TEST_F(PathNodeTest, TestMakePath) {
  auto_ptr<PathNode> root(new PathNode(PathNode::ROOT, "root"));
  PathNode* n1 = Insert(root.get(), new PathNode(PathNode::DIRECTORY, "n1"));
  EXPECT_EQ("n1", SafeMakePath(n1).toString());
  PathNode* n11 = Insert(n1, new PathNode(PathNode::DIRECTORY, "n11"));
  EXPECT_EQ("n1/n11", SafeMakePath(n11).toString());
  PathNode* n111 = Insert(n11, new PathNode(PathNode::FILE, "n111"));
  EXPECT_EQ("n1/n11/n111", SafeMakePath(n111).toString());
}

TEST_F(PathTreeTest, TestInsert) {
  PathTree::Node* node;
  node = tree->Insert("/dir/subdir");
  node = tree->Insert("file.ext", node);
  EXPECT_EQ("/dir/subdir/file.ext", tree->GetPath(node).toString());
}

TEST_F(PathTreeTest, TestIterator) {
  PathTree pt;
  pt.Insert("/foo/bar/file1");
  pt.Insert("/foo/bar/file2");
  pt.Insert("/foo/baz/file3");
  pt.Insert("/bar/file4");
  PathTree::Iterator it(&pt, pt.root());
  EXPECT_FALSE(it.done());
  EXPECT_EQ("", it->name());
  ++it;
  EXPECT_FALSE(it.done());
  EXPECT_EQ("foo", it->name());
  ++it;
  EXPECT_FALSE(it.done());
  EXPECT_EQ("bar", it->name());
  ++it;
  EXPECT_FALSE(it.done());
  EXPECT_EQ("file1", it->name());
  ++it;
  EXPECT_FALSE(it.done());
  EXPECT_EQ("file2", it->name());
  ++it;
  EXPECT_FALSE(it.done());
  EXPECT_EQ("baz", it->name());
  ++it;
  EXPECT_FALSE(it.done());
  EXPECT_EQ("file3", it->name());
  ++it;
  EXPECT_FALSE(it.done());
  EXPECT_EQ("bar", it->name());
  ++it;
  EXPECT_FALSE(it.done());
  EXPECT_EQ("file4", it->name());
  ++it;
  EXPECT_TRUE(it.done());
}

TEST_F(PathTreeTest, TestWalker) {
  tree->Insert("/testdata/skip/ignore.txt");
  tree->Insert("/testdata/skip/subdir/ignore.txt");
  MockWalker walker;
  int count = 0;
  for (PathTree::Node* node = tree->Next(tree->root(), &walker); node; node = tree->Next(node, &walker)) {
    ++count;
    EXPECT_NE("ignore.txt", node->name());
  }
  EXPECT_EQ(walker.enterCount, walker.exitCount);
  EXPECT_EQ(count, walker.enterCount + walker.fileCount);
  EXPECT_EQ(3, walker.enterCount);
  EXPECT_EQ(5, walker.fileCount);
}

TEST_F(PathTreeTest, TestWalkerIterator) {
  tree->Insert("/testdata/skip/ignore.txt");
  tree->Insert("/testdata/skip/subdir/ignore.txt");
  MockWalker walker;
  PathTree::Iterator(tree.get(), &walker).VisitAll();
  EXPECT_EQ(walker.enterCount, walker.exitCount);
  EXPECT_EQ(3, walker.enterCount);
  EXPECT_EQ(5, walker.fileCount);
}

TEST_F(PathTreeTest, TestWalkerFlat) {
  PathTree pt;
  pt.Insert("/testdata/file1");
  pt.Insert("/testdata/file2");
  pt.Insert("/testdata/file3");
  MockWalker walker;
  PathTree::Iterator(&pt, &walker).VisitAll();
  EXPECT_EQ(walker.enterCount, walker.exitCount);
  EXPECT_EQ(1, walker.enterCount);
  EXPECT_EQ(3, walker.fileCount);
}

TEST_F(PathTreeTest, TestFind) {
  EXPECT_EQ("afile.txt", tree->Find("/testdata/subdir/afile.txt")->name());
  EXPECT_EQ("subdir", tree->Find(Path("/testdata/subdir/afile.txt").parent())->name());
}

} // namespace filebow
